// Upsert_user 太过频繁会出内存异常，这里加上一层本地缓存，并且实施单读单存
package mongodb

import (
	"context"
	"fmt"
	"historyOfWikipedia/internal/model"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var (
	// 全局唯一
	ipObjectIDMap sync.Map
	// 全局唯一
	idUsernameObjectIDMap sync.Map
)

type MongoHistoryDataBase struct {
	ctx                        context.Context
	client                     *mongo.Client
	collection_kv_check        *mongo.Collection
	collection_users           *mongo.Collection
	collection_pages_articles  *mongo.Collection
	collection_revision_info   *mongo.Collection
	collection_revision_detail *mongo.Collection
	collection_revision_words  *mongo.Collection
	collection_revision_snap   *mongo.Collection
}

// type LoadRevisionPageSize struct {

func NewMongoHistoryDataBase(MongoUri string, version string) *MongoHistoryDataBase {
	client, err := mongo.NewClient(options.Client().ApplyURI(MongoUri))
	if err != nil {
		log.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client.Connect(ctx)
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}
	database := client.Database("wikipedia_history_load_" + version)
	cctx := context.Background()

	mongodb := MongoHistoryDataBase{
		ctx:                        cctx,
		client:                     client,
		collection_kv_check:        database.Collection("kv_chcek"),
		collection_users:           database.Collection("users"),
		collection_pages_articles:  database.Collection("pages_articles"),
		collection_revision_info:   database.Collection("revision_info"),
		collection_revision_detail: database.Collection("revision_detail"),
		collection_revision_words:  database.Collection("revision_words"),
		collection_revision_snap:   database.Collection("revision_snap"),
	}
	mongodb.initIndex()
	return &mongodb
}

func (c *MongoHistoryDataBase) initIndex() {
	mods := []mongo.IndexModel{
		{Keys: bson.M{"userID": 1}},
		{Keys: bson.M{"username": 1}},
		{Keys: bson.M{"ip": 1}},
		{Keys: bson.D{{Key: "userID", Value: 1}, bson.E{Key: "username", Value: 1}, bson.E{Key: "ip", Value: 1}}, Options: options.Index().SetUnique(true)},
	}
	_, err := c.collection_users.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_users index err:", err)
	}

	mods = []mongo.IndexModel{
		{Keys: bson.M{"ns": 1}},
		{Keys: bson.M{"redirect": 1}},
		{Keys: bson.M{"title": 1}},
		{Keys: bson.M{"revisionID": 1}},
		{Keys: bson.M{"lastRevisionUpdate": 1}},
		{Keys: bson.M{"firstRevisionUpdate": 1}},
	}
	_, err = c.collection_pages_articles.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_pages_articles index err:", err)
	}

	mods = []mongo.IndexModel{
		{Keys: bson.M{"revid": 1}},
		{Keys: bson.M{"articleID": 1}},
		{Keys: bson.M{"timestamp": 1}},
		{Keys: bson.M{"ns": 1}},
		{Keys: bson.D{bson.E{Key: "revid", Value: 1}, bson.E{Key: "articleID", Value: 1}}, Options: options.Index().SetUnique(true)},
	}
	_, err = c.collection_revision_words.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_revision_info index err:", err)
	}

	mods = []mongo.IndexModel{
		{Keys: bson.M{"revid": 1}},
		{Keys: bson.M{"articleID": 1}},
		{Keys: bson.M{"user": 1}},
		{Keys: bson.M{"timestamp": 1}},
		{Keys: bson.M{"texthidden": 1}},
		{Keys: bson.D{bson.E{Key: "revid", Value: 1}, bson.E{Key: "articleID", Value: 1}}, Options: options.Index().SetUnique(true)},
	}
	_, err = c.collection_revision_info.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_revision_info index err:", err)
	}

	mods = []mongo.IndexModel{
		{Keys: bson.M{"revid": 1}},
		{Keys: bson.M{"articleID": 1}},
		{Keys: bson.M{"timestamp": 1}},
		{Keys: bson.M{"snapshot": 1}},
		{Keys: bson.D{bson.E{Key: "revid", Value: 1}, bson.E{Key: "articleID", Value: 1}}, Options: options.Index().SetUnique(true)},
	}
	_, err = c.collection_revision_detail.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_revision_info index err:", err)
	}

	mods = []mongo.IndexModel{
		{Keys: bson.M{"revid": 1}},
		{Keys: bson.M{"articleID": 1}},
		{Keys: bson.M{"timestamp": 1}},
		{Keys: bson.M{"snapshot": 1}},
		{Keys: bson.D{bson.E{Key: "revid", Value: 1}, bson.E{Key: "articleID", Value: 1}}, Options: options.Index().SetUnique(true)},
	}
	_, err = c.collection_revision_snap.Indexes().CreateMany(c.ctx, mods)
	if err != nil {
		log.Warn("collection_revision_info index err:", err)
	}

}

func (c *MongoHistoryDataBase) Insert_pages_articles(doc *model.LoadPagesArticlesObj) error {
	_, err := c.collection_pages_articles.InsertOne(c.ctx, doc)
	if err != nil && mongo.IsDuplicateKeyError(err) {
		return nil
	}
	return err
}

func (c *MongoHistoryDataBase) Insert_kv(path string) {
	c.collection_kv_check.InsertOne(c.ctx, bson.M{"_id": path})
}

func (c *MongoHistoryDataBase) Get_kv(path string) bool {
	result := c.collection_kv_check.FindOne(c.ctx, bson.M{"_id": path})
	return result.Err() != mongo.ErrNoDocuments
}

func (c *MongoHistoryDataBase) Upsert_user_by_ip(ip string) (objID primitive.ObjectID, err error) {
	value, _ := ipObjectIDMap.Load(ip)
	if value == nil {
		opt := options.Update().SetUpsert(true)
		var result *mongo.UpdateResult
		for i := 0; i < 3; i++ {
			result, err = c.collection_users.UpdateOne(c.ctx, bson.M{"ip": ip}, bson.M{"$set": bson.M{"ip": ip}}, opt)
			if err == nil {
				if result.UpsertedID != nil {
					ipObjectIDMap.Store(ip, result.UpsertedID.(primitive.ObjectID))
					return result.UpsertedID.(primitive.ObjectID), err
				} else {
					var doc model.OnlyIDObj
					findResult := c.collection_users.FindOne(c.ctx, bson.M{"ip": ip})
					findResult.Decode(&doc)
					ipObjectIDMap.Store(ip, doc.ID)
					return doc.ID, findResult.Err()
				}
			} else {
				log.Warn("wait 1s,updateone err:", err)
				<-time.After(time.Second)
			}
		}
	} else {
		return value.(primitive.ObjectID), nil
	}

	log.Warn("Upsert_user_by_name_id updateone err:", err)
	return
}

func (c *MongoHistoryDataBase) Upsert_user_by_name_id(name string, id int64) (objID primitive.ObjectID, err error) {
	mapKey := fmt.Sprintf("%d-%s", id, name)
	value, _ := idUsernameObjectIDMap.Load(mapKey)
	if value == nil {
		opt := options.Update().SetUpsert(true)
		var result *mongo.UpdateResult
		for i := 0; i < 3; i++ {
			result, err = c.collection_users.UpdateOne(c.ctx, bson.M{"userID": id, "username": name}, bson.M{"$set": bson.M{"userID": id, "username": name}}, opt)
			if err == nil {
				if result.UpsertedID != nil {
					idUsernameObjectIDMap.Store(mapKey, result.UpsertedID.(primitive.ObjectID))
					return result.UpsertedID.(primitive.ObjectID), err
				} else {
					var doc model.OnlyIDObj
					findResult := c.collection_users.FindOne(c.ctx, bson.M{"userID": id, "username": name})
					findResult.Decode(&doc)
					idUsernameObjectIDMap.Store(mapKey, doc.ID)
					return doc.ID, findResult.Err()
				}
			} else {
				log.Warn("wait 1s,upsert error:", err)
				<-time.After(time.Second)
			}
		}
	} else {
		return value.(primitive.ObjectID), nil
	}
	log.Warn("Upsert_user_by_name_id updateone err:", err)
	return
}

func (c *MongoHistoryDataBase) Insert_many_revision_info(revisions []*model.LoadRevisionInfoObj) error {

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}

	for i := range revisions {
		models = append(models, mongo.NewUpdateOneModel().SetFilter(bson.M{
			"revid":     revisions[i].Revid,
			"articleID": revisions[i].ArticleID,
		}).SetUpdate(bson.M{"$set": revisions[i]}).SetUpsert(true))
	}

	retTmp, err := c.collection_revision_info.BulkWrite(c.ctx, models, opts)
	if err != nil {
		log.Println("bulk upsert fail", err)
		log.Printf("%+v", retTmp)
	}

	return err
}

func (c *MongoHistoryDataBase) Insert_many_revision_words(revisions []*model.LoadRevisionWords) error {

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}

	for i := range revisions {
		models = append(models, mongo.NewUpdateOneModel().SetFilter(bson.M{
			"revid":     revisions[i].Revid,
			"articleID": revisions[i].ArticleID,
		}).SetUpdate(bson.M{"$set": revisions[i]}).SetUpsert(true))
	}

	retTmp, err := c.collection_revision_words.BulkWrite(c.ctx, models, opts)
	if err != nil {
		log.Println("bulk upsert fail", err)
		log.Printf("%+v", retTmp)
	}

	return err
}

func (c *MongoHistoryDataBase) Insert_many_revision_detail(revisions []*model.LoadRevisionDetailObj) error {

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}

	for i := range revisions {
		models = append(models, mongo.NewUpdateOneModel().SetFilter(bson.M{
			"revid":     revisions[i].Revid,
			"articleID": revisions[i].ArticleID,
		}).SetUpdate(bson.M{"$set": revisions[i]}).SetUpsert(true))
	}

	retTmp, err := c.collection_revision_detail.BulkWrite(c.ctx, models, opts)
	if err != nil {
		log.Println("bulk upsert fail", err)
		log.Printf("%+v", retTmp)
	}

	return err
}

func (c *MongoHistoryDataBase) Insert_many_revision_snap(revisions []*model.LoadRevisionDetailObj) error {

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}

	for i := range revisions {
		models = append(models, mongo.NewUpdateOneModel().SetFilter(bson.M{
			"revid":     revisions[i].Revid,
			"articleID": revisions[i].ArticleID,
		}).SetUpdate(bson.M{"$set": revisions[i]}).SetUpsert(true))
	}

	retTmp, err := c.collection_revision_snap.BulkWrite(c.ctx, models, opts)
	if err != nil {
		log.Println("bulk upsert fail", err)
		log.Printf("%+v", retTmp)
	}

	return err
}
